// 无重复字符的最长子串.js
```
+---+---+---+---+---+---+---+---+---+
| a | b | c | a | b | c | b | b |   |
+---+---+---+---+---+---+---+---+---+
  ↑                       ↑
 left                  right

```;

function lengthOfLongestSubstring(s) {
  let n = s.length;
  let set = new Set();
  let maxLength = 0;
  let left = 0;
  // 当窗口内出现重复字符时，移动左指针缩小窗口
  for (let right = 0; right < n; right++) {
    // 当窗口内出现重复字符时，移动左指针缩小窗口
    while (set.has(s[right])) {
      set.delete(s[left]);
      left++;
    }
    // 将字符添加到窗口中
    set.add(s[right]);
    maxLength = Math.max(maxLength, right - left + 1);
  }

  return maxLength;
}

// 测试
console.log(lengthOfLongestSubstring("abcabcbb")); // 输出 3 ("abc")
console.log(lengthOfLongestSubstring("bbbbb")); // 输出 1 ("b")
console.log(lengthOfLongestSubstring("pwwkew")); // 输出 3 ("wke")
console.log(lengthOfLongestSubstring("")); // 输出 0

function longestSubstring(s) {
    let charSet = new Set(); // 用于存储窗口内的字符
    let maxLength = 0;
    let left = 0;
    let start = 0; // 记录最长子串的起始位置

    for (let right = 0; right < s.length; right++) {
        // 当窗口内出现重复字符时，移动左指针缩小窗口
        while (charSet.has(s[right])) {
            charSet.delete(s[left]);
            left++;
        }
        
        // 将字符添加到窗口中
        charSet.add(s[right]);
        
        // 更新最长子串的长度和起始位置
        if (right - left + 1 > maxLength) {
            maxLength = right - left + 1;
            start = left;
        }
    }

    return s.substring(start, start + maxLength);
}

// 测试
console.log(longestSubstring("abcabcbb")); // 输出 "abc"（最长子串是 "abc"）

